﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using blackhouse.Data;
using System.Data;
using System.Runtime.Caching;
using System.Diagnostics;
using System.Collections;

namespace blackhouse.WebNemo.Data.Cache {
    internal class NemoCache {

        #region Fields

        private Stack<CacheSettings> configurations = new Stack<CacheSettings>();

        private TimeSpan cacheInterval;

        private MsSqlAdoDataHelper dataHelper;

        private static ObjectCache c = MemoryCache.Default;

        #endregion

        #region Properties

        private CacheSettings CurrentSetting {
            get {
                return configurations.Peek();
            }
        }

        #endregion

        #region Constructors

        public NemoCache(TimeSpan cacheInterval, string sqlConnectionString) {
            this.cacheInterval = cacheInterval;
            this.dataHelper = new MsSqlAdoDataHelper(sqlConnectionString);
        }

        #endregion

        #region Methods

        public void SetKey(string key) {
            this.CurrentSetting.Key = key;
        }

        public void SetParameters(params object[] parameters) {
            this.CurrentSetting.QueryIdentifier = GetS(parameters);
        }

        private string GetS(object[] parameters) {
            List<string> ss = new List<string>();
            foreach (object item in parameters) {
                if (item == null) continue;
                if (item is Array)
                    ss.Add(GetS((object[])item));
                else
                    ss.Add(item.ToString());
            }
            string q = string.Join("-", ss);
            return q;
        }

        public void SetDependencyTables(params int[] groups) {
            this.CurrentSetting.DependOnTables = groups;
        }

        public void Reset() {
            CacheSettings cs = new CacheSettings();
            this.configurations.Push(cs);
            this.CurrentSetting.Watch.Start();
        }

        public void End() {
            Debug.WriteLine(string.Format("{0:F} ms - {1}", this.CurrentSetting.Watch.Elapsed.TotalMilliseconds, this.GetCurrentCacheKey()));
            CacheSettings cs = this.configurations.Pop();
            cs.Watch.Stop();
            cs.Watch = null;
        }

        public void TouchTimes() {
            c.Remove("NemoTimes");
        }

        public T GetData<T>() {
            NemoCacheObject cache = c.Get(this.GetCurrentCacheKey()) as NemoCacheObject;
            if (cache == null) return default(T);
            DateTime dbTime = this.GetLastUpdateTime();
            if (cache.SetTime < dbTime) return default(T);
            return (T)cache.CachedData;

        }

        internal DateTime GetLastUpdateTime() {
            var q = from t in this.GetTimes()
                    join tb in this.CurrentSetting.DependOnTables on t.GroupId equals tb
                    select t.LastUpdateTime;
            if (q.Count() == 0)
                return DateTime.Now;
            else
                return q.Max();
        }

        internal string GetCurrentCacheKey() {
            return "{key:" + this.CurrentSetting.Key + "}" + string.Join("-", this.CurrentSetting.DependOnTables) + "_" + this.CurrentSetting.QueryIdentifier;
        }

        private IEnumerable<NemoTableTime> GetTimes() {
            IEnumerable<NemoTableTime> times = c.Get("NemoTimes") as IEnumerable<NemoTableTime>;
            if (times == null) {
                times = this.GetTimesFromDb();
                c.Set("NemoTimes", times, new CacheItemPolicy() { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(2), Priority = CacheItemPriority.NotRemovable });
            }
            return times;
        }

        private IEnumerable<NemoTableTime> GetTimesFromDb() {
            this.dataHelper.Reset();
            this.dataHelper.ProcedureName = "bhp_NemoTablesLastModificationTimeGet";
            return this.dataHelper.Execute<NemoTableTime>(ReadNemoTableTimeFields);
        }

        private NemoTableTime ReadNemoTableTimeFields(IDataReader reader) {
            int groupId;
            DateTime lastUpdate = DateTime.MinValue;
            groupId = Convert.ToInt32(reader["GroupId"]);
            if (!reader.IsDBNull(reader.GetOrdinal("LastTime")))
                lastUpdate = Convert.ToDateTime(reader["LastTime"]);
            return new NemoTableTime(groupId, lastUpdate);
        }

        public void RefreshTime() {
            this.RefreshTime(this.CurrentSetting.DependOnTables);
        }

        public void RefreshTime(int groupId) {
            this.RefreshTime(new int[] { groupId });
        }

        public void RefreshTime(params int[] groupIds) {
            IEnumerable<NemoTableTime> times = c.Get("NemoTimes") as IEnumerable<NemoTableTime>;
            if (times == null) return;
            var q = from g in groupIds
                    join t in times on g equals t.GroupId
                    select t;
            foreach (NemoTableTime table in q)
                table.LastUpdateTime = DateTime.Now;

            c.Set("NemoTimes", times, new CacheItemPolicy() { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(2), Priority = CacheItemPriority.NotRemovable });
        }

        public void SetCaching(object data) {
            c.Set(this.GetCurrentCacheKey(), new NemoCacheObject(data), new CacheItemPolicy() { SlidingExpiration = this.cacheInterval });
        }

        #endregion

        private class CacheSettings {
            public string Key;
            public int[] DependOnTables;
            public string QueryIdentifier;
            public Stopwatch Watch = new Stopwatch();
        }

    }
}
